﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace client.app
{
    using TcpMessageFuncDic = Dictionary<comm.objst.TcpMessageType, Action<comm.objst.TcpMessage>>;

    public static class TcpClient
    {
        public static bool Connected
        {
            get
            {
                return _tcpClient != null && _tcpClient.Connected;
            }
        }
        public static int SendFPS { get { return _sendTimeStat.FPS; } }
        public static long SendTotalSize { get; private set; }
        public static long SendCurrSize { get; private set; }
        public static int ReceiveFPS { get { return _receiveTimeStat.FPS; } }
        public static long ReceiveTotalSize { get; private set; }
        public static long ReceiveCurrSize { get; private set; }

        static TcpMessageFuncDic _tcpMessageDic = new TcpMessageFuncDic();
        static comm.TcpClient _tcpClient = null;
        static comm.UniqueID _uid = new comm.UniqueID();
        static long _fillSpaceTime = -1;
        static Queue<Action> _taskQ = new Queue<Action>();
        static Dictionary<string, Action<comm.objst.TcpMessage>> _callbackDic = new Dictionary<string, Action<comm.objst.TcpMessage>>();
        static Action<bool> _connectCallback = null;
        static comm.TimeStat _sendTimeStat = new comm.TimeStat(), _receiveTimeStat = new comm.TimeStat();
        static comm.Timing _sendTiming = new comm.Timing(), _receiveTiming = new comm.Timing();
        static long _sendCurr = 0, _receiveCurr = 0;

        static TcpClient()
        {
            var type = typeof(TcpClient);
            var enumType = typeof(comm.objst.TcpMessageType);
            var fields = enumType.GetFields();
            foreach (var fd in fields)
            {
                if (fd.Name.IndexOf("ToClient") == 0)
                {
                    var md = type.GetMethod(fd.Name, BindingFlags.NonPublic | BindingFlags.Static);
                    if (md != null)
                    {
                        var mtype = (comm.objst.TcpMessageType)Enum.Parse(enumType, md.Name);
                        _tcpMessageDic.Add(mtype, message =>
                        {
                            md.Invoke(null, new object[] { message });
                        });
                    }
                    else
                    {
                        throw new Exception("StartListen " + fd.Name + " not found");
                    }
                }
            }

            comm.ThreadHelper.Run(() =>
            {
                while (true)
                {
                    if (_taskQ.Count > 0)
                    {
                        _taskQ.Dequeue()();
                    }
                    Thread.Sleep(1);
                }
            });
        }

        public static void Connect(Action<bool> callback)
        {
            _connectCallback = callback;
            comm.TcpClient.Connect(Conf.TCP_SERVER_IP, Conf.TCP_SERVER_PORT, OnConnect);
        }

        //#region ToClient
        static void ToClientCallback(comm.objst.TcpMessage message)
        {
            var msgId = message.MsgId;
            lock (_callbackDic)
            {
                if (_callbackDic.ContainsKey(msgId))
                {
                    var ac = _callbackDic[msgId];
                    _callbackDic.Remove(msgId);
                    if (ac != null)
                    {
                        ac(message);
                    }
                }
            }
        }
        static void ToClientCreateSpace(comm.objst.TcpMessage message)
        {
            AddTask(() =>
            {
                message.TryToObject<string[]>(arr =>
                {
                    var mapId = arr[0];
                    var starId = arr[1];
                    var spaceId = arr[2];
                    Console.WriteLine("create space mapId:" + mapId + " starId:" + starId + " spaceId:" + spaceId);
                    MainGame.MainSpace.CreateSpace(mapId, starId, spaceId);
                });
            });
        }
        static void ToClientSpaceFillObject(comm.objst.TcpMessage message)
        {
            _fillSpaceTime = -1;
            AddTask(() =>
            {
                message.TryToObject<comm.objst.SObjectInfo[]>(sobjInfos =>
                {
                    Console.WriteLine("SpaceFillObject objs:" + sobjInfos.Length);
                    //Console.WriteLine(comm.JsonSerializer.Serialize(sobjInfos));

                    MainGame.MainSpace.Clear();
                    MainGame.MainSpace.Space.ClearAllSObject();
                    foreach (var sobjInfo in sobjInfos)
                    {
                        var sobj = MainGame.MainSpace.Space.CreateSObject(sobjInfo);
                        var moveTo = sobjInfo.MoveTo;
                        if (moveTo != null)
                        {
                            sobj.SetLoc(moveTo.FromX, moveTo.FromY);
                            sobj.BeginMove(moveTo.ToX, moveTo.ToY, (int)moveTo.TimeOffset);
                        }
                        else
                        {
                            sobj.SetLoc(sobjInfo.LocX, sobjInfo.LocY);
                        }
                    }
                    _fillSpaceTime = message.SendTime;
                });
            });
        }
        static void ToClientSpaceObjectEnter(comm.objst.TcpMessage message)
        {
            if (_fillSpaceTime == -1 || message.SendTime < _fillSpaceTime)
            {
                return;
            }
            AddTask(() =>
            {
                Console.WriteLine("SpaceObjectEnter");
                message.TryToObject<comm.objst.SObjectInfo[]>(sobjInfos =>
                {
                    foreach (var sobjInfo in sobjInfos)
                    {
                        var sobj = MainGame.MainSpace.Space.CreateSObject(sobjInfo);
                        sobj.SetLoc(sobjInfo.LocX, sobjInfo.LocY);
                    }
                });
            });
        }
        static void ToClientSpaceObjectLeave(comm.objst.TcpMessage message)
        {
            if (_fillSpaceTime == -1 || message.SendTime < _fillSpaceTime)
            {
                return;
            }
            AddTask(() =>
            {
                Console.WriteLine("SpaceObjectLeave");
                message.TryToObject<string[]>(objIds =>
                {
                    MainGame.MainSpace.Space.RemoveSObjects(objIds);
                });
            });
        }
        static void ToClientSpaceObjectBeginMove(comm.objst.TcpMessage message)
        {
            if (_fillSpaceTime == -1 || message.SendTime < _fillSpaceTime)
            {
                return;
            }
            AddTask(() =>
            {
                message.TryToObject<comm.objst.MoveTo[]>(moveTos =>
                {
                    foreach (var moveTo in moveTos)
                    {
                        var sobj = MainGame.MainSpace.Space.GetSObject(moveTo.ObjectId);
                        if (sobj != null)
                        {
                            var checkLoc = sobj.LocX == moveTo.FromX && sobj.LocY == moveTo.FromY;
                            if (sobj.MoveType == comm.objst.MoveType.FLIGHT)
                            {
                                sobj.BeginMove(moveTo.ToX, moveTo.ToY, message.TimeOffset);
                            }
                            else
                            {
                                if (checkLoc)
                                {
                                    var steps = new int[][] { 
                                        new int[] { moveTo.FromY, moveTo.FromY }, 
                                        new int[] { moveTo.ToX, moveTo.ToY } };
                                    sobj.BeginMoveSteps(steps, message.TimeOffset);
                                }
                                else
                                {
                                    sobj.BeginMove(moveTo.ToX, moveTo.ToY, message.TimeOffset);
                                }
                            }
                        }
                    }
                });
            });
        }
        //#endregion

        static void AddTask(Action callback)
        {
            _taskQ.Enqueue(callback);
        }

        public static void Send(comm.objst.TcpMessageType type, object obj, string msgId = null)
        {
            _sendTimeStat.Update();

            if (_tcpClient == null)
            {
                MessageBox.Show("TcpClient Send() Client Not connected");
                return;
            }
            var msg = new comm.objst.TcpMessage
            {
                Type = type,
                Obj = obj,
                SendTime = DateTime.Now.Ticks,
                MsgId = msgId != null ? msgId : ""
            };
            //if (GameManager.Instance.MainSpace != null)
            //{
            //    msg.TimeOffset = GameManager.Instance.MainSpace.Ping.TimeOffset;
            //}
            var size = _tcpClient.SendAsync(comm.BinarySerializer.Serialize(msg));
            SendTotalSize += size;
            _sendCurr += size;
            if (_sendTiming.GetElapsedTime(_sendTimeStat, 1000) > 0)
            {
                SendCurrSize = _sendCurr;
                _sendCurr = 0;
            }
        }
        public static void Call(comm.objst.TcpMessageType type, object obj, Action<comm.objst.TcpMessage> callback)
        {
            var msgId = _uid.NewId();
            lock (_callbackDic)
            {
                _callbackDic.Add(msgId, callback);
            }
            Send(type, obj, msgId);
        }
        public static void Call<T>(comm.objst.TcpMessageType type, object obj, Action<T, comm.objst.TcpMessage> callback)
        {
            Call(type, obj, message =>
            {
                message.TryToObject<T>(t =>
                {
                    callback(t, message);
                });
            });
        }

        static void OnConnect(bool succ, comm.TcpClient client)
        {
            if (succ)
            {
                _tcpClient = client;
                _tcpClient.OnCloseCallback = OnClose;
                _tcpClient.OnReceiveCallback = OnReceive;
            }

            _connectCallback(succ);
            _connectCallback = null;
        }
        static void OnReceive(comm.TcpClient client, byte[] data)
        {
            _receiveTimeStat.Update();
            ReceiveTotalSize += data.Length;
            _receiveCurr += data.Length;
            if (_receiveTiming.GetElapsedTime(_receiveTimeStat, 1000) > 0)
            {
                ReceiveCurrSize = _receiveCurr;
                _receiveCurr = 0;
            }

            try
            {
                var message = comm.BinarySerializer.Deserialize<comm.objst.TcpMessage>(data);
                if (_tcpMessageDic.ContainsKey(message.Type))
                {
                    try
                    {
                        _tcpMessageDic[message.Type](message);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("PlayerManager ClientReceive() type:" + message.Type + " " + ex.Message + " \r\n" + ex.StackTrace, comm.ConsoleColor.RED);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("PlayerManager TcpClientReceive() " + ex.Message);
            }
        }
        static void OnClose(comm.TcpClient client)
        {
            Console.WriteLine("server close");
        }
    }
}